在上一篇文章中,我們介紹了新版裝飾器與舊版裝飾器的差異。這一篇將更深入探討新版裝飾器的實際應用。要運行本文範例,請確保 TypeScript 版本為 5 以上,並且 experimentalDecorators
與 emitDecoratorMetadata
均未啟用或設為 false
。
TypeScript 5 除了不再支援舊版的參數裝飾器(Parameter Decorator)外,還多增加了更多細節的裝飾器,目前裝飾器的種類有:
Class Decorator
:類別裝飾器Method Decorator
:方法裝飾器Field Decorator
:屬性裝飾器Accessor Decorator
:存取器裝飾器Getter Decorator
:取值裝飾器Setter Decorator
:設值裝飾器TypeScript 5 類別裝飾器用法跟舊版來比較相差不多,只是多了一個 context
參數,並有提供 ClassDecoratorContext<T>
型別來定義 context
。
下面的範例是使用裝飾器注入 createTime
,並利用 interface
的自動型別合併功能擴充 createTime
型別。
function withCreateTime<T extends new (...args: any[]) => {}>(
baseClass: T,
context: ClassDecoratorContext<T>
) {
return class extends baseClass {
createTime: Date = new Date();
constructor(...args: any[]) {
super(...args);
}
};
}
@withCreateTime
class Person {
name: string = '阿毛';
}
interface Person {
createTime: Date;
}
const person = new Person();
console.log(person.createTime); // Sat Sep 21 2024 19:24:58 GMT+0800 (台北標準時間)
TypeScript 5 方法裝飾器用法跟舊版差很多,舊版的 target
會提供被裝飾類別的建構子,新版的 target
則是傳遞被裝飾的函式,且 propertyKey
與 descriptor
也移除了,改成使用型別 ClassMethodDecoratorContext<T, V>
定義的 context
。
下面的範例我們使用的裝飾器工廠實作,並裝飾 addTodo
限制新增代辦事項的條件,就能在不改動類別的情況下,注入新的邏輯進去。
function limitTodo(limit: number) {
return function <
T extends { todos: Todo[] },
V extends (this: T, ...args: any) => any
>(target: Function, context: ClassMethodDecoratorContext<T, V>) {
return function (this: T, ...args: any[]) {
const unfinishedTodos = this.todos.filter((todo) => !todo.finished);
if (unfinishedTodos.length < limit) {
return target.apply(this, args);
}
console.log("請先完成其他待辦事項");
};
};
}
interface Todo {
name: string;
finished: boolean;
}
class TodoList {
todos: Todo[] = [];
@limitTodo(2)
addTodo(name: string) {
this.todos.push({ name, finished: false });
}
}
const todoList = new TodoList();
todoList.addTodo("吃飯");
todoList.addTodo("睡覺");
todoList.addTodo("打東東"); // 請先完成其他待辦事項
TypeScript 5 屬性裝飾器就跟舊版的寫法差很多,舊版的 target
會提供被裝飾類別的建構子,新版的 target
則是傳遞 undefined
,且新版可以回傳一個函式,這個函式會收到類別裝飾的屬性。
需注意此時的 this
會因為類別是在初始化的狀態下,所以當下有些值會是 undefined
,不建議在裡面使用 this
。
function initExampleTodos<T, V extends Todo[]>(
target: undefined,
context: ClassFieldDecoratorContext<T, V>
) {
return function (todos: V) {
todos.push({ name: "完成寫鐵人賽文章", finished: false });
return todos;
};
}
interface Todo {
name: string;
finished: boolean;
}
class TodoList {
@initExampleTodos
todos: Todo[] = [];
}
const todoList = new TodoList();
console.log(todoList.todos); // [{ name: "完成寫鐵人賽文章", finished: false }]
TypeScript 5 存取器裝飾器是新出的裝飾器,同時提供了三個型別讓我們可以定義,分別是 ClassAccessorDecoratorTarget
、ClassAccessorDecoratorContext
、ClassAccessorDecoratorResult
,accessor
關鍵字是用來幫屬性自動設定存取器的,也就是會自動編譯成 getter
與 setter
,而存取器裝飾器讓我們能用更細緻的方式去對屬性做裝飾。
下面的範例就是過去幾篇我們用屬性裝飾器來實作的範例,使用存取器裝飾器可以讓我們更快速優雅地做到這件事。
class Person {
@watchValue
accessor name: string = "阿毛";
}
function watchValue<T, V>(
target: ClassAccessorDecoratorTarget<T, V>,
context: ClassAccessorDecoratorContext<T, V>
): ClassAccessorDecoratorResult<T, V> {
return {
get(this: T) {
return target.get.call(this);
},
set(this: T, value: V) {
console.log(`設置 ${context.name.toString()} 屬性值從 ${target.get.call(this)} 改為 ${value}`)
target.set.call(this, value);
}
}
}
const person = new Person();
person.name = "Mao"; // 設置 name 屬性值從 阿毛 改為 Mao
TypeScript 5 還有針對 Getter 與 Setter 做出新的裝飾器,用法也非常簡單,所以將這兩個裝飾器一起說明,target
都是各自的 getter
或 setter
函式,需要注意的是,因為都是對物件操作,所以要透過 call
、apply
或 bind
的方式來綁定 this
來執行。
class Person {
private _name: string = "阿毛";
@greet
get name() {
return this._name;
}
@logSetter
set name(value) {
this._name = value;
}
}
function greet<T, V>(
target: () => V,
context: ClassGetterDecoratorContext<T, V>
) {
return function (this: T) {
return `我的名字是 ${target.call(this)}`;
};
}
function logSetter<T, V>(
target: (value: V) => void,
context: ClassSetterDecoratorContext<T, V>
) {
return function (this: T, value: V) {
console.log(`名字改成 ${value}`);
target.call(this, value);
};
}
const person = new Person();
console.log(person.name); // 我的名字是 阿毛
person.name = "Mao"; // 名字改成 Mao
console.log(person.name); // 我的名字是 Mao
在這篇文章中,我們詳細介紹了新版 TypeScript 5 裝飾器的實際應用,包括類別、方法、屬性、存取器及其細化的 getter 和 setter 裝飾器。